
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Threading;

using Swaf;
using Swaf.Config;
using Swaf.Container;
using Swaf.BizRule;

namespace Swaf.BizRule.ThreadedQueue
{
	/// <summary>
	/// Summary description for ThreadedCompositeMonitor.
	/// </summary>
	public class ThreadedCompositeMonitor : ICompositeMonitor
	{
		protected IList m_children;
		protected LinkedList m_waitingNotifications;
		protected Semaphore m_notificationCountingSem;
		protected ReaderWriterLock m_childrenLock;

		public ThreadedCompositeMonitor()
		{
			m_children = new ArrayList();
			m_waitingNotifications = new LinkedList();
			m_childrenLock = new ReaderWriterLock();
			m_notificationCountingSem = new Semaphore(0);
			Thread notifyThread = new Thread(new ThreadStart(doNotification));
			notifyThread.IsBackground = true;
			notifyThread.Start();
		}

		protected void doNotification()
		{
			while(true)
			{
				m_notificationCountingSem.acquire();
				RuleCallInfo info = null;
				lock(m_waitingNotifications)
				{
					info = (RuleCallInfo)m_waitingNotifications.Head.Element;
					m_waitingNotifications.remove(m_waitingNotifications.Head);
				}
				if(info != null)
				{
					lock(m_children)
					{
						switch(info.type)
						{
							case CallType.enqueued:
								foreach(IRuleMonitor mon in m_children)
									mon.ruleEnqueued(info.time, info.callId, info.isNewRuleset, info.ruleName, info.info);
								break;
							case CallType.started:
								foreach(IRuleMonitor mon in m_children)
									mon.ruleStarted(info.time, info.callId, info.ruleName, info.info);
								break;
							case CallType.completed:
								foreach(IRuleMonitor mon in m_children)
									mon.ruleCompleted(info.time, info.callId, info.ruleName);
								break;
							case CallType.failed:
								foreach(IRuleMonitor mon in m_children)
									mon.ruleFailed(info.time, info.callId, info.ruleName, (Exception)info.info);
								break;
							default:
								throw new Exception("An unknown CallType was used. Update this switch statement.");
								break;
						}
					}
				}
			}
		}

		public void removeMonitor(IRuleMonitor monitor)
		{
			lock(m_children)
			{
				m_children.Remove(monitor);
			}
		}

		public void addMonitor(IRuleMonitor monitor)
		{
			lock(m_children)
			{
				m_children.Add(monitor);
			}
		}

		public void ruleEnqueued(DateTime enqueueTime, int callId, bool isNewRuleset, string ruleName, object callParams)
		{
			lock(m_waitingNotifications)
			{
				m_waitingNotifications.addTail(new RuleCallInfo(CallType.enqueued, enqueueTime, callId, ruleName, callParams, isNewRuleset));
			}
			m_notificationCountingSem.release();
		}

		public void ruleCompleted(DateTime completeTime, int callId, string ruleName)
		{
			lock(m_waitingNotifications)
			{
				m_waitingNotifications.addTail(new RuleCallInfo(CallType.completed, completeTime, callId, ruleName, null, false));
			}
			m_notificationCountingSem.release();
		}

		public void ruleFailed(DateTime failTime, int callId, string ruleName, Exception errInfo)
		{
			lock(m_waitingNotifications)
			{
				m_waitingNotifications.addTail(new RuleCallInfo(CallType.failed, failTime, callId, ruleName, errInfo, false));
			}
			m_notificationCountingSem.release();
		}

		public void ruleStarted(DateTime startTime, int callId, string ruleName, object callParams)
		{
			lock(m_waitingNotifications)
			{
				m_waitingNotifications.addTail(new RuleCallInfo(CallType.started, startTime, callId, ruleName, callParams, false));
			}
			m_notificationCountingSem.release();
		}
	}

	internal enum CallType{enqueued, started, completed, failed};

    internal class RuleCallInfo
	{
		public CallType type;
		public DateTime time;
		public int callId;
		public string ruleName;
		public object info;
		public bool isNewRuleset;

		public RuleCallInfo(CallType type, DateTime time, int callId, string ruleName, object info, bool isNewRuleset)
		{
			this.type = type;
			this.time = time;
			this.callId = callId;
			this.ruleName = ruleName;
			this.info = info;
			this.isNewRuleset = isNewRuleset;
		}
	}
}
